// Created by plusminus on 11:01:15 PM - Feb 26, 2009
package org.anddev.arcc.checkers;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.anddev.arcc.adt.Inconsistency;

/**
 * @author Nicolas Gramlich
 */
public abstract class AbstractBaseChecker {
	// ===========================================================
	// Constants
	// ===========================================================
	
	// ===========================================================
	// Fields
	// ===========================================================
	
	protected ExecutorService mThreadPool = Executors.newFixedThreadPool(10);
	
	protected final String[] mFolders;
	protected Map<String, List<String>> mResourceRepresentations = new HashMap<String, List<String>>();
	protected File mProjectResourceDir;

	// ===========================================================
	// Constructors
	// ===========================================================
	
	public AbstractBaseChecker(final File projectResourceDir, final String[] pFolders) {
		this.mProjectResourceDir = projectResourceDir;
		this.mFolders = pFolders;
	}

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	// ===========================================================
	// Methods from SuperClass/Interfaces
	// ===========================================================
	
	protected abstract String getBaseDirectory();
	protected abstract Map<String, List<String>> checkFile(final String pResourceFolder, final File pFile);
	protected abstract FilenameFilter getFilenameFilter();

	// ===========================================================
	// Methods
	// ===========================================================
	
	protected void spawnFileCheckerThead(final String pResourceFolder, final File pFile) {
		this.mThreadPool.execute(new Runnable(){
			@Override
			public void run() {
				/* Avoid concurrency problems. */
				synchronized (AbstractBaseChecker.this.mResourceRepresentations) {
					Map<String, List<String>> items = checkFile(pResourceFolder, pFile);
					
					
					for(Iterator<Entry<String, List<String>>> i = items.entrySet().iterator(); i.hasNext(); ){
						final Entry<String, List<String>> e = i.next();
						
						final List<String> existing = AbstractBaseChecker.this.mResourceRepresentations.get(e.getKey());
						
						/* If no representations existed before, add a new List. */
						if(existing != null){
							existing.addAll(e.getValue());
						}else{
							final List<String> newList = new ArrayList<String>();
							newList.add(pResourceFolder);
							AbstractBaseChecker.this.mResourceRepresentations.put(e.getKey(), newList);
						}
					}
				}
			}		
		});
	}
	
	public List<Inconsistency> run(){
		this.parse();
		
		return checkForInconsistencies();
	}
	
	protected List<Inconsistency> checkForInconsistencies() {
		final List<Inconsistency> out = new ArrayList<Inconsistency>();
		
		final String baseDirectory = getBaseDirectory();
		
		for(Iterator<Entry<String, List<String>>> it = this.mResourceRepresentations.entrySet().iterator(); it.hasNext();){
			final Entry<String, List<String>> current = it.next();
			
			/* Check if the hierarchy of the representations is consistent. */
			final List<String> representations = current.getValue();
			
			boolean existsInBaseDir = false;
			for(String rep : representations)
				existsInBaseDir = existsInBaseDir || baseDirectory.compareTo(rep) == 0;
			
			if(!existsInBaseDir)
				out.add(new Inconsistency(current.getKey(), representations));
		}
		
		return out;
	}
	
	public void parse(){		
		System.out.println("");
		System.out.println("Started: '" + getBaseDirectory() + "' parsing.");
		
		for (String f : this.mFolders) {
			/* Loop through all children and check if they are directories. */
			final File folder = new File(this.mProjectResourceDir, f);
			if(folder.isDirectory()){
				final String valuesFolderName = folder.getName();
				
				/* Only files ending with ".xml". */
				final String[] files = folder.list(getFilenameFilter());
				
				/* For each of the XML-files, spawn a thread. */
				for (String filename : files) {
					final File resourceFile = new File(folder, filename);
					if(resourceFile.isFile()){
						spawnFileCheckerThead(valuesFolderName, resourceFile);	
					}	
				}
			}
		}
		
		System.out.println("Waiting for all Threads to terminate.");
		try {
			this.mThreadPool.shutdown();
			this.mThreadPool.awaitTermination(60, TimeUnit.MINUTES);
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.exit(0);
		}
		
		System.out.println("'" + getBaseDirectory() + "' contained: " + this.mResourceRepresentations.size() + " resources.");
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================
}
